package com.jse.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.List;

import javax.tools.DiagnosticCollector;
import javax.tools.DiagnosticListener;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
 
/**
 * 运行时编译
 */
public class Compiler {
    private JavaFileManager fileManager;
 
    public Compiler() {
        this.fileManager = initManger();
    }
 
    private JavaFileManager initManger() {
        if (fileManager != null) {
            return fileManager;
        } else {
            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            DiagnosticListener<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector();
            fileManager = new ClassFileManager(javaCompiler.getStandardFileManager(diagnosticCollector, null, null));
            return fileManager;
        }
    }
 
    /**
     * 编译源码并加载，获取Class对象
     * @param path
     * @return
     * @throws ClassNotFoundException
     */
    public Class compileAndLoad(String path) throws ClassNotFoundException {
    	String name=path.charAt(0)=='/'?path.substring(1):path;
    	return compileAndLoad(name.replace('/','.'), Fs.readString(path));
    }
    /**
     * 编译源码并加载，获取Class对象
     * @param fullName
     * @param sourceCode
     * @return
     * @throws ClassNotFoundException
     */
    public Class compileAndLoad(String fullName, String sourceCode) throws ClassNotFoundException {
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        List<JavaFileObject> javaFileObjectList = new ArrayList<JavaFileObject>();
        javaFileObjectList.add(new CharSequenceJavaFileObject(fullName, sourceCode));
        boolean result = javaCompiler.getTask(null, fileManager, null, null, null, javaFileObjectList).call();
        if (result) {
            return this.fileManager.getClassLoader(null).loadClass(fullName);
        } else {
            return Class.forName(fullName);
        }
    }
 
    /**
     * 关闭fileManager
     * @throws IOException
     */
    public void close() throws IOException {
        this.fileManager.close();
    }
 
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, InvocationTargetException {
		var ip=new Compiler().compileAndLoad("com.jse.IP",Fs.readString("D:\\work\\jsa\\src\\com\\jse\\IP.java"));
		var m=ip.getMethod("ip");
		System.out.println(m.invoke(null));
    }
    
    private class ClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
    	 
        /**
         * 存储编译后的代码数据
         */
        private JavaClassObject classJavaFileObject;
     
        protected ClassFileManager(JavaFileManager fileManager) {
            super(fileManager);
        }
     
        /**
         * 编译后加载类
         * <p>
         * 返回一个匿名的SecureClassLoader:
         * 加载由JavaCompiler编译后，保存在ClassJavaFileObject中的byte数组。
         */
        @Override
        public ClassLoader getClassLoader(Location location) {
            return new SecureClassLoader() {
                @Override
                protected Class<?> findClass(String name) throws ClassNotFoundException {
                    byte[] bytes = classJavaFileObject.getBytes();
                    return super.defineClass(name, bytes, 0, bytes.length);
                }
            };
        }
     
        /**
         * 给编译器提供JavaClassObject，编译器会将编译结果写进去
         */
        @Override
        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            this.classJavaFileObject = new JavaClassObject(className, kind);
            return this.classJavaFileObject;
        }
     
    }
    
    private class CharSequenceJavaFileObject extends SimpleJavaFileObject {
    	 
        //表示java源代码
        private CharSequence content;
     
        protected CharSequenceJavaFileObject(String className, String content) {
            super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
            this.content = content;
        }
     
        /**
         * 获取需要编译的源代码
         * @param ignoreEncodingErrors
         * @return
         * @throws IOException
         */
        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            return content;
        }
    }
    
    private class JavaClassObject extends SimpleJavaFileObject {
    	 
        /**
         * Compiler编译后的byte数据会存在这个ByteArrayOutputStream对象中，
         * 后面可以取出，加载到JVM中。
         */
        private ByteArrayOutputStream byteArrayOutputStream;
     
        public JavaClassObject(String className, Kind kind) {
            super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);
            this.byteArrayOutputStream = new ByteArrayOutputStream();
        }
     
        /**
         * 覆盖父类SimpleJavaFileObject的方法。
         * 该方法提供给编译器结果输出的OutputStream。
         * 
         * 编译器完成编译后，会将编译结果输出到该 OutputStream 中，我们随后需要使用它获取编译结果
         *
         * @return
         * @throws IOException
         */
        @Override
        public OutputStream openOutputStream() throws IOException {
            return this.byteArrayOutputStream;
        }
     
        /**
         * FileManager会使用该方法获取编译后的byte，然后将类加载到JVM
         */
        public byte[] getBytes() {
            return this.byteArrayOutputStream.toByteArray();
        }
    }
}